#! /usr/bin/env python
# encoding: utf-8

# Generates the system call header from a simple description

import sys
import getopt


help_message = '''
This tool generates a header and trampoline functions for system calls.
This allows system calls to be defined in a simple way,
including the specification of arguments and the system call number.

  syscallgen.py -i input_file -o output_file_prefix
'''

class Usage(Exception):
    def __init__(self, msg):
        self.msg = msg



class UnexpectedToken(Exception):
    def __init__(self, token):
        self.msg = "Unexpected token '" + token + "'"

class EarlyEOF(Exception):
    def __init__(self, msg):
        self.msg = msg

class Syscall(object):
    def __init__(self, name, ret, arglist):
        self.name = name
        self.rettype = ret
        self.args = arglist
        self.kv_data = {}
    def setKeyValue(self, key, val):
        self.kv_data[key] = val
    def getValueForKey(self, key):
        return self.kv_data[key]

    def __str__(self):
        return "<syscall: %s %s ( %s ): %s>" % (self.rettype, self.name, self.args, self.kv_data)

    def getName(self):
        return self.name
    def getRetType(self):
        return ' '.join(self.rettype)
    def getArgCount(self):
        return len(self.args)
    def getArgType(self, idx):
        a, b = self.args[idx]
        return ' '.join(a)
    def getArgName(self, idx):
        a, b = self.args[idx]
        return b

class Tokeniser(object):
    def __init__(self, file_data):
        tokens = file_data.split()
        self.tokens = tokens
    def next_token(self):
        if len(self.tokens) >= 1:
            tok = self.tokens[0]
            self.tokens = self.tokens[1:]
            return tok
        return None
    def next_token_or_die(self):
        tok = self.next_token()
        if tok == None:
            raise EarlyEOF("Tried to parse a system call def, but EOF was found")
        return tok

    # Check next token and assrt if not valid
    def assert_next_token(self, tok):
        next_tok = self.next_token_or_die() 
        if next_tok != tok:
            raise UnexpectedToken(tok)
        return next_tok

    # Check and consume if tok is valid
    def check_next_token(self, tok):
        next_tok = self.tokens[0] 
        if next_tok != tok:
            return False
        self.tokens = self.tokens[1:]
        return True

    # Peek but do not consume
    def peek_next_token(self, tok):
        next_tok = self.tokens[0] 
        if next_tok != tok:
            return False
        return True

def parse_syscall(tokeniser):
    rettype = []
    # Parse return type
    tok = tokeniser.next_token_or_die()
    while tok[0] != '@':
        rettype = rettype + [tok]
        tok = tokeniser.next_token_or_die()

    # Parse name
    syscall_name = tok[1:]
    arg_types = []

    # Parse arguments
    tokeniser.assert_next_token("(")
    while not tokeniser.check_next_token(")"):
        arg = []
        while (not tokeniser.check_next_token(",")) and not tokeniser.peek_next_token(")"):
            tok = tokeniser.next_token_or_die()
            arg = arg + [tok]
        arg_type = arg[0:-2]
        arg_name = arg[-1]
        arg_types = arg_types + [(arg_type, arg_name)]

    tokeniser.assert_next_token(":")

    sc = Syscall(syscall_name, rettype, arg_types)
    # Parse kw list
    while not tokeniser.check_next_token("end"):
        if not tokeniser.peek_next_token("end"):
            key = tokeniser.next_token_or_die()
            eq = tokeniser.assert_next_token("=")
            value = tokeniser.next_token_or_die()
            if value == "end":
                raise UexpectedToken(value)
            sc.setKeyValue(key, value)

    tokeniser.assert_next_token(syscall_name)

    return sc

def parse_syscalls(infile):
    syscalls = []
    file_data = infile.read()
    tokeniser = Tokeniser(file_data)
    while (tokeniser.next_token() == "def"):
        syscalls = syscalls + [parse_syscall(tokeniser)]
    return syscalls


def gen_header(syscalls, outfile):
    outfile.write("#ifndef KERNEL__SYSCALL_PARAMS\n")
    outfile.write("#define KERNEL__SYSCALL_PARAMS\n")
    outfile.write("// AUTOMATICALLY GENERATED, AVOID EDITING BY HAND\n\n")
    for sc in syscalls:
        outfile.write("typedef struct {\n")
        for i in range(0, sc.getArgCount()):
            outfile.write("  %s %s;\n" % (sc.getArgType(i), sc.getArgName(i)) )
        outfile.write("} %s_params_t;\n\n" % (sc.getName()))
    outfile.write("#endif /* ! KERNEL__SYSCALL_PARAMS */\n")

def gen_user_trampolines(syscalls, header, outfile):
    outfile.write("// AUTOMATICALLY GENERATED, AVOID EDITING BY HAND\n")

    outfile.write("#include <%s>\n\n" % (header))
    for sc in syscalls:
        outfile.write("%s\n" % (sc.getRetType()))
        outfile.write("syscall_%s(" % (sc.getName()))
        for i in range(0, sc.getArgCount()-1):
            outfile.write("%s %s, " % (sc.getArgType(i), sc.getArgName(i)) )
        outfile.write("%s %s)\n{\n" % (sc.getArgType(sc.getArgCount()-1),
                                         sc.getArgName(sc.getArgCount()-1)))

        outfile.write("  %s_params_t params = {\n    " % (sc.getName()))
        for i in range(0, sc.getArgCount()-1):
            outfile.write("%s," % (sc.getArgName(i)))
        outfile.write("%s };\n" % (sc.getArgName(sc.getArgCount()-1)))
        outfile.write("  %s result;\n\n" % (sc.getRetType()))
        outfile.write("  __asm__ volatile (\n")
        outfile.write("    \"mov r0, %[result]\\n\"\n")
        outfile.write("    \"mov r1, %[params]\\n\"\n")
        outfile.write("    \"svc #%s\\n\"\n" % sc.getValueForKey("id"))
        outfile.write("  : /* no out */\n")
        outfile.write("  : [result] \"r\" (&result), [params] \"r\" (&params) // ins\n")
        outfile.write("  : \"r0\", \"r1\"); // Clobber\n\n")
        outfile.write("  return result;\n")
        outfile.write("}\n\n")

def gen_kernel_stubs(syscalls, header, outfile):
    outfile.write("// This is a helper file, you should fill in the rest of the code\n")
    outfile.write("#include <%s>\n\n" % (header))

    for sc in syscalls:
        outfile.write("void\nsys_%s(%s *result, %s_params_t *params)\n{\n" % (sc.getName(), sc.getRetType(),
                                                                      sc.getName()))
        outfile.write("  // PUT CODE HERE\n}\n\n")

def gen_kernel_dispatch(syscalls, outfile):
    outfile.write("// This is a helper file, copy the function here to the syscall.c file\n\n")
    outfile.write("void\nsyscall(unsigned callId, void *result, void *params)\n{\n")
    outfile.write("  switch (callId) {\n")
    for sc in syscalls:
        outfile.write("  case %s:\n" % (sc.getValueForKey("id")))
        outfile.write("    sys_%s(result, params);\n" % (sc.getName()))
        outfile.write("    break;\n")
    outfile.write("  default:\n")
    outfile.write("    /* TODO: Insert code to kill user process */;\n")
    outfile.write("  }\n")
    outfile.write("}\n\n")

def syscallgen(infile, outfile):
    input_file = open(infile, "r")
    header_file = open(outfile + "__params.h", "w")
    usertramp_file = open(outfile + "__trampolines.c", "w")
    kernelstubs_file = open(outfile + "__stubs.c", "w")
    kerneldispatch_file = open(outfile + "__dispatch.c", "w")
    
    syscalls = parse_syscalls(input_file)
    
    gen_header(syscalls, header_file)
    gen_user_trampolines(syscalls, outfile + "__params.h", usertramp_file)
    gen_kernel_stubs(syscalls, outfile + "__params.h", kernelstubs_file)
    gen_kernel_dispatch(syscalls, kerneldispatch_file)
    
def main(argv=None):
    output = None
    file_name = None
    if argv is None:
        argv = sys.argv
    try:
        try:
            opts, args = getopt.getopt(argv[1:], "ho:vi:f:", ["help", "output=", "input="])
        except getopt.error, msg:
            raise Usage(msg)

        # option processing
        for option, value in opts:
            if option == "-v":
                verbose = True
            if option in ("-h", "--help"):
                raise Usage(help_message)
            if option in ("-o", "--output"):
                output = value
            if option in ("-i", "-f", "--input"):
                file_name = value

        if not file_name:
          raise Usage(help_message)
        if not output:
          raise Usage(help_message)
    except Usage, err:
        print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg)
        print >> sys.stderr, "\t for help use --help"
        return 2

    syscallgen(file_name, output)


if __name__ == "__main__":
  sys.exit(main())
